package sv.gob.mh.dgii.xml;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BasicDynaBean;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import sv.gob.mh.dgii.common.Utils;

public class XmlDynaClass implements DynaClass, Serializable {

    private static final long serialVersionUID = 1437845190429351620L;
    protected DynaProperty properties[] = null;
    protected List<DynaBean> rows = new ArrayList<DynaBean>();
    protected Map<String, DynaProperty> propertiesMap = new HashMap<String, DynaProperty>();
    private boolean useClass = false;
    Log log = LogFactory.getLog(XmlDynaClass.class);

    public XmlDynaClass(String xml, boolean useClass) throws Exception {
        if (xml == null) {
            throw new NullPointerException();
        }
        this.useClass = useClass;
        Document doc = DocumentHelper.parseText(xml);
        introspect(doc);
        copy(doc);
    }

    public XmlDynaClass() {
    }

    public XmlDynaClass(String xml) throws Exception {
        this(xml, false);
    }

    public List getRows() {
        return (this.rows);
    }


    /*
     * (sin Javadoc)
     * 
     * @see org.apache.commons.beanutils.DynaClass#getName()
     */
    @Override
    public String getName() {
        return (this.getClass().getName());
    }

    /*
     * (sin Javadoc)
     * 
     * @see org.apache.commons.beanutils.DynaClass#getDynaProperty(java.lang.String)
     */
    @Override
    public DynaProperty getDynaProperty(String name) {
        if (name == null) {
            throw new IllegalArgumentException("No property name specified");
        }
        return ((DynaProperty) propertiesMap.get(name));

    }

    /*
     * (sin Javadoc)
     * 
     * @see org.apache.commons.beanutils.DynaClass#getDynaProperties()
     */
    @Override
    public DynaProperty[] getDynaProperties() {
        return (properties);
    }

    /*
     * (sin Javadoc)
     * 
     * @see org.apache.commons.beanutils.DynaClass#newInstance()
     */
    @Override
    public DynaBean newInstance() throws IllegalAccessException,
            InstantiationException {
        throw new UnsupportedOperationException("newInstance() not supported");
    }

    protected Class loadClass(String className) throws Exception {
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            if (cl == null) {
                cl = this.getClass().getClassLoader();
            }
            return (cl.loadClass(className));
        } catch (Exception ex) {
            throw new Exception("Cannot load column class '" + className + "': " + ex);
        }

    }

    protected DynaProperty createDynaProperty(Element column) throws Exception {

        String name = column.getText();
        String className = column.attributeValue(XmlConstants.CLASS);
        Class clazz = String.class;
        if (className != null && useClass) {
            clazz = loadClass(className);
        }
        return new DynaProperty(name, clazz);

    }

    protected void introspect(Document doc) throws Exception {
        Element root = doc.getRootElement();
        Element columns = root.element(XmlConstants.COLUMNS);
        Iterator it = columns.elementIterator(XmlConstants.COLUMN);
        ArrayList<DynaProperty> list = new ArrayList<DynaProperty>();
        while (it.hasNext()) {
            Element column = (Element) it.next();
            DynaProperty dynaProperty = createDynaProperty(column);
            if (dynaProperty != null) {
                list.add(dynaProperty);
            }
        }
        properties = (DynaProperty[]) list.toArray(new DynaProperty[list.size()]);



        for (int i = 0; i < properties.length; i++) {
            propertiesMap.put(properties[i].getName(), properties[i]);
        }

    }

    protected void copy(Document doc) throws Exception {
        Element root = doc.getRootElement();
        Element rows = root.element(XmlConstants.ROWS);
        Iterator it = rows.elementIterator(XmlConstants.ROW);

        while (it.hasNext()) {
            Element row = (Element) it.next();
            DynaBean bean = createDynaBean();
            Iterator itColumns = row.elementIterator(XmlConstants.COLUMN);
            int i = 0;
            while (itColumns.hasNext()) {
                Element column = (Element) itColumns.next();
                String name = properties[i].getName();
                Class type = properties[i].getType();
                if (useClass) {
                    bean.set(name, createPropertyValue(type, column.getText()));
                } else {
                    bean.set(name, column.getText());
                }
                i++;
            }
            this.rows.add(bean);
        }

    }

    protected DynaBean createDynaBean() {
        return (new BasicDynaBean(this));
    }

    protected Object createPropertyValue(Class type, String value) {
        Object ret = null;
        //log.debug("Class: '" + type + "' , value: '" + value + "'");
        if (Utils.isNullOrEmpty(value)) {
            ret = null;
        } else {

            if (type.equals(String.class)) {
                ret = value;
            } else if (type.equals(int.class) || type.equals(Integer.class)) {
                ret = Integer.valueOf(value);
            } else if (type.equals(long.class) || type.equals(Long.class)) {
                ret = Long.valueOf(value);
            } else if (type.equals(float.class) || type.equals(Float.class)) {
                ret = Float.valueOf(value);
            } else if (type.equals(double.class) || type.equals(Double.class)) {
                ret = Double.valueOf(value);
            } else if (type.equals(BigDecimal.class)) {
                ret = new BigDecimal(value);
            } else if (type.equals(Timestamp.class)) {
                ret = Timestamp.valueOf(value);
            }
        }
        return ret;
    }
}
